/*-------------------------------------------------------------------------
 *
 * nodeProjectSet.c
 *	  支持评估包含集合返回函数的目标列表
 *
 * DESCRIPTION
 *
 *		ProjectSet节点由规划者插入以评估目标列表中的集合返回函数。确保所有集合返回函数
 *		都直接位于目标列表的顶层，即它们不能嵌套在更复杂的表达式中。如果确实如此，规划者
 *		会添加额外的ProjectSet节点。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * IDENTIFICATION
 *	  src/backend/executor/nodeProjectSet.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "executor/executor.h"
#include "executor/nodeProjectSet.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "utils/memutils.h"


static TupleTableSlot *fc_ExecProjectSRF(ProjectSetState *fc_node, bool fc_continuing);


/* ----------------------------------------------------------------
 *		ExecProjectSet(node)
 *
 *		在评估目标列表（包含集合返回函数）后返回元组。
 * ----------------------------------------------------------------
 */
static TupleTableSlot * fc_ExecProjectSet(PlanState *fc_pstate)
{
	ProjectSetState *fc_node = castNode(ProjectSetState, fc_pstate);
	TupleTableSlot *fc_outerTupleSlot;
	TupleTableSlot *fc_resultSlot;
	PlanState  *fc_outerPlan;
	ExprContext *fc_econtext;

	CHECK_FOR_INTERRUPTS();

	fc_econtext = fc_node->ps.ps_ExprContext;

	/*
	 * 重置每个元组上下文以释放为可能之前返回的元组分配的表达式评估存储。请注意，SRF 参数上下文具有不同的生命周期，并在下面重置。
	 */
	ResetExprContext(fc_econtext);

	/*
	 * 检查我们是否仍然在从先前扫描元组中投影元组（因为投影表达式中有一个函数返回集合）。如果是，则尝试投影另一个。
	 */
	if (fc_node->pending_srf_tuples)
	{
		fc_resultSlot = fc_ExecProjectSRF(fc_node, true);

		if (fc_resultSlot != NULL)
			return fc_resultSlot;
	}

	/*
	 * 获取另一个输入元组并从中投影 SRFs。
	 */
	for (;;)
	{
		/*
		 * 重置参数上下文以释放在先前元组周期中分配的任何表达式评估存储。请注意，这不能在我们完成从扫描元组投影元组之前发生，因为 ValuePerCall 函数允许引用每个返回元组的参数。然而，如果我们在发现扫描元组未生成任何行后循环回去，我们应该重置，以避免当许多连续扫描元组不产生行时出现内存泄漏。
		 */
		MemoryContextReset(fc_node->argcontext);

		/*
		 * 从外部计划中检索元组，直到没有更多为止。
		 */
		fc_outerPlan = outerPlanState(fc_node);
		fc_outerTupleSlot = ExecProcNode(fc_outerPlan);

		if (TupIsNull(fc_outerTupleSlot))
			return NULL;

		/*
		 * 准备计算投影表达式，这些表达式将期望以 varno OUTER 访问输入元组。
		 */
		fc_econtext->ecxt_outertuple = fc_outerTupleSlot;

		/* 评估表达式 */
		fc_resultSlot = fc_ExecProjectSRF(fc_node, false);

		/*
		 * 返回元组，除非投影没有产生行（由于集合为空），在这种情况下我们必须循环回去查看是否还有更多 outerPlan 元组。
		 */
		if (fc_resultSlot)
			return fc_resultSlot;

		/*
		 * 当我们循环回去时，最好再次重置 econtext，以防 SRF 在那里泄漏了一些内存。
		 */
		ResetExprContext(fc_econtext);
	}

	return NULL;
}

/* ----------------------------------------------------------------
 *		ExecProjectSRF
 *
 *		投影包含一个或多个集合返回函数的目标列表。
 *
 *		'continuing' 表示是否继续为相同输入元组投影行；或者是否正在投影新的输入元组。
 *
 *		如果没有生成输出元组，则返回 NULL。
 *
 * ----------------------------------------------------------------
 */
static TupleTableSlot * fc_ExecProjectSRF(ProjectSetState *fc_node, bool fc_continuing)
{
	TupleTableSlot *fc_resultSlot = fc_node->ps.ps_ResultTupleSlot;
	ExprContext *fc_econtext = fc_node->ps.ps_ExprContext;
	MemoryContext fc_oldcontext;
	bool		fc_hassrf PG_USED_FOR_ASSERTS_ONLY;
	bool		fc_hasresult;
	int			fc_argno;

	ExecClearTuple(fc_resultSlot);

	/* 在每个元组上下文中调用 SRFs 以及普通表达式 */
	fc_oldcontext = MemoryContextSwitchTo(fc_econtext->ecxt_per_tuple_memory);

	/*
	 * 假设除非遇到来自集合返回函数的 ExprMultipleResult，否则不会生成进一步的元组。
	 */
	fc_node->pending_srf_tuples = false;

	fc_hassrf = fc_hasresult = false;
	for (fc_argno = 0; fc_argno < fc_node->nelems; fc_argno++)
	{
		Node	   *fc_elem = fc_node->elems[fc_argno];
		ExprDoneCond *fc_isdone = &fc_node->elemdone[fc_argno];
		Datum	   *fc_result = &fc_resultSlot->tts_values[fc_argno];
		bool	   *fc_isnull = &fc_resultSlot->tts_isnull[fc_argno];

		if (fc_continuing && *fc_isdone == ExprEndResult)
		{
			/*
			 * 如果我们继续从源元组投影输出行，一旦 SRF 被耗尽，则返回 NULL。
			 */
			*fc_result = (Datum) 0;
			*fc_isnull = true;
			fc_hassrf = true;
		}
		else if (IsA(fc_elem, SetExprState))
		{
			/*
			 * 评估 SRF - 可能继续先前开始的输出。
			 */
			*fc_result = ExecMakeFunctionResultSet((SetExprState *) fc_elem,
												fc_econtext, fc_node->argcontext,
												fc_isnull, fc_isdone);

			if (*fc_isdone != ExprEndResult)
				fc_hasresult = true;
			if (*fc_isdone == ExprMultipleResult)
				fc_node->pending_srf_tuples = true;
			fc_hassrf = true;
		}
		else
		{
			/* 非 SRF 的 tlist 表达式，仅正常评估。 */
			*fc_result = ExecEvalExpr((ExprState *) fc_elem, fc_econtext, fc_isnull);
			*fc_isdone = ExprSingleResult;
		}
	}

	MemoryContextSwitchTo(fc_oldcontext);

	/* 如果没有 SRFs，则不应使用 ProjectSet */
	Assert(fc_hassrf);

	/*
	 * 如果所有 SRFs 返回 ExprEndResult，我们认为没有生成行。
	 */
	if (fc_hasresult)
	{
		ExecStoreVirtualTuple(fc_resultSlot);
		return fc_resultSlot;
	}

	return NULL;
}

/* ----------------------------------------------------------------
 *		ExecInitProjectSet
 *
 *		为规划器产生的 ProjectSet 节点创建运行时状态信息，并初始化外部关系（子节点）。
 * ----------------------------------------------------------------
 */
ProjectSetState *
ExecInitProjectSet(ProjectSet *fc_node, EState *fc_estate, int fc_eflags)
{
	ProjectSetState *fc_state;
	ListCell   *fc_lc;
	int			fc_off;

	/* 检查不支持的标志 */
	Assert(!(fc_eflags & (EXEC_FLAG_MARK | EXEC_FLAG_BACKWARD)));

	/*
	 * 创建状态结构
	 */
	fc_state = makeNode(ProjectSetState);
	fc_state->ps.plan = (Plan *) fc_node;
	fc_state->ps.state = fc_estate;
	fc_state->ps.ExecProcNode = fc_ExecProjectSet;

	fc_state->pending_srf_tuples = false;

	/*
	 * 杂项初始化
	 *
	 * 为节点创建表达式上下文
	 */
	ExecAssignExprContext(fc_estate, &fc_state->ps);

	/*
	 * 初始化子节点
	 */
	outerPlanState(fc_state) = ExecInitNode(outerPlan(fc_node), fc_estate, fc_eflags);

	/*
	 * 我们不使用内部计划
	 */
	Assert(innerPlan(fc_node) == NULL);

	/*
	 * 元组表和结果类型初始化
	 */
	ExecInitResultTupleSlotTL(&fc_state->ps, &TTSOpsVirtual);

	/* 为每个 tlist 项的表达式状态和 SRF 完成状态创建工作区 */
	fc_state->nelems = list_length(fc_node->plan.targetlist);
	fc_state->elems = (Node **)
		palloc(sizeof(Node *) * fc_state->nelems);
	fc_state->elemdone = (ExprDoneCond *)
		palloc(sizeof(ExprDoneCond) * fc_state->nelems);

	/*
	 * 构建表达式以评估目标列表。我们不能在这里使用 ExecBuildProjectionInfo，因为那不处理 SRFs。相反，单独编译每个表达式，适用时使用 ExecInitFunctionResultSet。
	 */
	fc_off = 0;
	foreach(fc_lc, fc_node->plan.targetlist)
	{
		TargetEntry *fc_te = (TargetEntry *) lfirst(fc_lc);
		Expr	   *fc_expr = fc_te->expr;

		if ((IsA(fc_expr, FuncExpr) && ((FuncExpr *) fc_expr)->funcretset) ||
			(IsA(fc_expr, OpExpr) && ((OpExpr *) fc_expr)->opretset))
		{
			fc_state->elems[fc_off] = (Node *)
				ExecInitFunctionResultSet(fc_expr, fc_state->ps.ps_ExprContext,
										  &fc_state->ps);
		}
		else
		{
			Assert(!expression_returns_set((Node *) fc_expr));
			fc_state->elems[fc_off] = (Node *) ExecInitExpr(fc_expr, &fc_state->ps);
		}

		fc_off++;
	}

	/* 我们不支持 ProjectSet 节点上的任何 qual */
	Assert(fc_node->plan.qual == NIL);

	/*
	 * 创建 ExecMakeFunctionResultSet 可以用来评估函数参数的内存上下文。我们不能为此使用每个元组上下文，因为它重置得太频繁；但我们也不想将评估结果泄漏到查询生命周期上下文中。我们为所有 tSRFs 的参数使用一个上下文，因为它们的生命周期大致相同。
	 */
	fc_state->argcontext = AllocSetContextCreate(CurrentMemoryContext,
											  "tSRF function arguments",
											  ALLOCSET_DEFAULT_SIZES);

	return fc_state;
}

/* ----------------------------------------------------------------
 *		ExecEndProjectSet
 *
 *		释放通过 C 例程分配的存储
 * ----------------------------------------------------------------
 */
void ExecEndProjectSet(ProjectSetState *fc_node)
{
	/*
	 * 释放表达式上下文
	 */
	ExecFreeExprContext(&fc_node->ps);

	/*
	 * 清空元组表
	 */
	ExecClearTuple(fc_node->ps.ps_ResultTupleSlot);

	/*
	 * 关闭子计划
	 */
	ExecEndNode(outerPlanState(fc_node));
}

void ExecReScanProjectSet(ProjectSetState *fc_node)
{
	/* 忘记任何未完全评估的 SRFs */
	fc_node->pending_srf_tuples = false;

	/*
	 * 如果子节点的 chgParam 不为 null，则计划将通过
	 * 第一个 ExecProcNode 重新扫描。
	 */
	if (fc_node->ps.lefttree->chgParam == NULL)
		ExecReScan(fc_node->ps.lefttree);
}
